#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <sys/select.h>
#include "mytbf.h"

struct mytbf_st
{
    int cps;
    int burst;
    int pos;
    int token;
    pthread_mutex_t mut;
    pthread_cond_t cond;
};

static void selectAlrm(int sec)
{
   struct timeval tm;
   tm.tv_sec = sec;
   tm.tv_usec = 0;

   select(0, NULL, NULL, NULL, &tm);
}

pthread_mutex_t jobmut = PTHREAD_MUTEX_INITIALIZER;
static struct mytbf_st *job[MYTBF_MAX];
static pthread_t tid;
pthread_once_t once = PTHREAD_ONCE_INIT;

void *thrAlrm(void *args)
{
    int i;
    while (1)
    {
        pthread_mutex_lock(&jobmut);
        for (i = 0; i < MYTBF_MAX; i++)
        {
            if (job[i] != NULL)
            {
                pthread_mutex_lock(&job[i]->mut);
                job[i]->token += job[i]->cps;
                if (job[i]->token > job[i]->burst)
                    job[i]->token = job[i]->burst;
                pthread_cond_broadcast(&job[i]->cond);
                pthread_mutex_unlock(&job[i]->mut);
            }
        }
        pthread_mutex_unlock(&jobmut);
    }
    selectAlrm(1);
}

void mode_unload()
{
    pthread_cancel(tid);
    pthread_join(tid, NULL);

    int i;
    for (i = 0; i < MYTBF_MAX; i++)
    {
        free(job[i]);
    }
}

void mode_load()
{
    int err;
    err = pthread_create(&tid, NULL, thrAlrm, NULL);
    if (err < 0)
    {
        fprintf(stderr, "pthread_create():%s", strerror(errno));
        exit(1);
    }

    atexit(mode_unload);
}

static int getFreePos()
{
    int i;
    for (i = 0; i < MYTBF_MAX; i++)
    {
        if (job[i] == NULL)
            return i;
    }
    return -1;
}

mytbf_t *mytbf_init(int cps,int burst)
{
    int pos;
    struct mytbf_st *me = malloc(sizeof(struct mytbf_st));

    pthread_once(&once, mode_load);
    if (me == NULL)
    {
       return NULL;
    }
    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    pthread_mutex_init(&me->mut, NULL);
    pthread_cond_init(&me->cond, NULL);

    pthread_mutex_lock(&jobmut);
    pos = getFreePos();
    if (pos < 0)
    {
        free(me);
        pthread_mutex_unlock(&jobmut);
        return NULL;
    }
    me->pos = pos;
    job[me->pos] = me;
    pthread_mutex_unlock(&jobmut);

    return me;
}

static int mymin(int a, int b)
{
    return a < b ? a : b;
}

int mytbf_fetchtoken(mytbf_t *me,int size)
{
    struct mytbf_st *ptr = me;

    pthread_mutex_lock(&ptr->mut);
    if (ptr->token <= 0)
        pthread_cond_wait(&ptr->cond, &ptr->mut);
    int n;
    n = mymin(ptr->token, size);
    ptr->token -= n;
    pthread_mutex_unlock(&ptr->mut);

    return n;
}

int mytbf_returntoken(mytbf_t *me,int n)
{
    struct mytbf_st *ptr = me;

    pthread_mutex_lock(&ptr->mut);
    ptr->token += n;
    if (ptr->token >= ptr->burst)
        ptr->token = ptr->burst;
    pthread_cond_broadcast(&ptr->cond);
    pthread_mutex_unlock(&ptr->mut);

    return 0;
}

int mytbf_destory(mytbf_t *me)
{
   struct mytbf_st *ptr = me;

   pthread_mutex_lock(&jobmut);
   job[ptr->pos] = NULL;
   pthread_mutex_unlock(&jobmut);

   pthread_mutex_destroy(&ptr->mut);
   pthread_cond_destroy(&ptr->cond);

   free(me);
   return 0;
}